package kayleeDiskLog

import (
	"cmp"
	"errors"
	"fmt"
	"github.com/kaylee595/tracerr"
	"github.com/samber/lo"
	"io"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"sync"
	"syscall"
	"time"
)

// Handler 将数据writer到文件, 当达到设定大小时, 将备份该文件, 并打开新的文件.
type Handler struct {
	filename   string
	file       *os.File
	fileMu     *sync.Mutex
	size       int64
	maxSize    int64
	maxBackups int
}

// NewDefaultHandler 将数据写到logs/log.log文件中, 文件大小超过1M将会转到备份, 文件备份数量最多1个.
// 等同于 NewHandler("", 0, 1) / NewHandler("logs/log.log", 1024*1024, 1)
func NewDefaultHandler() *Handler {
	return NewHandler("", 0, 1)
}

func NewHandler(filename string, maxSize int64, maxBackups int) *Handler {
	return &Handler{
		filename:   cmp.Or(filename, "logs/log.log"),
		maxSize:    cmp.Or(maxSize, 1024*1024), // 默认1M
		fileMu:     &sync.Mutex{},
		maxBackups: maxBackups,
	}
}

func (h *Handler) Write(p []byte) (int, error) {
	h.fileMu.Lock()
	defer h.fileMu.Unlock()

	if int64(len(p)) > h.maxSize {
		return 0, tracerr.New("数据太大, 不允许写入文件")
	}
	if h.file == nil {
		err := h.openFile()
		if err != nil {
			return 0, tracerr.Wrap(err)
		}
	}
	if h.size+int64(len(p)) > h.maxSize {
		err := h.backUp()
		if err != nil {
			return 0, tracerr.Wrap(err)
		}
	}
	write, err := h.file.Write(p)
	h.size += int64(write)
	if h.size > h.maxSize {
		err = h.backUp()
		if err != nil {
			return 0, tracerr.Wrap(err)
		}
	}
	return write, tracerr.Wrap(err)
}

func (h *Handler) openFile() error {
	file, err := os.OpenFile(h.filename, os.O_RDWR|os.O_CREATE, 0666)
	if err != nil {
		if !errors.Is(err, syscall.ERROR_PATH_NOT_FOUND) {
			return tracerr.Wrap(err)
		}
		err = os.MkdirAll(filepath.Dir(h.filename), 0666)
		if err != nil {
			return tracerr.Wrap(err)
		}
		file, err = os.OpenFile(h.filename, os.O_RDWR|os.O_CREATE, 0666)
		if err != nil {
			return tracerr.Wrap(err)
		}
	}
	h.file = file

	// 计算文件大小
	_, err = file.Seek(0, io.SeekStart)
	if err != nil {
		return tracerr.Wrap(err)
	}
	size, err := file.Seek(0, io.SeekEnd)
	if err != nil {
		return tracerr.Wrap(err)
	}
	h.size = size
	return nil
}

// closeFile 该函数协程不安全
func (h *Handler) closeFile() {
	if h.file == nil {
		return
	}
	h.file.Close()
	h.file = nil
	h.size = 0
}

// backUp 该函数协程不安全
func (h *Handler) backUp() error {
	if h.maxBackups <= 0 {
		err := h.file.Truncate(0)
		if err != nil {
			return tracerr.Wrap(err)
		}
		_, err = h.file.Seek(0, io.SeekStart)
		if err != nil {
			return tracerr.Wrap(err)
		}
		h.size = 0
		err = h.cleanBackupFiles()
		if err != nil {
			return tracerr.Wrap(err)
		}
		return nil
	}
	h.closeFile()
	basePath, name := filepath.Split(h.filename)
	ext := filepath.Ext(h.filename)
	var nameWithoutExt string
	if len(ext) > 0 {
		nameWithoutExt = name[:len(name)-len(ext)]
	} else {
		nameWithoutExt = name
	}
	var newFilename string
	var counter int
	for {
		counter++
		if counter == 1 {
			newFilename = fmt.Sprintf("%s.%s%s", nameWithoutExt, time.Now().Format("01-02"), ext)
		} else {
			newFilename = fmt.Sprintf("%s.%s(%d)%s", nameWithoutExt, time.Now().Format("01-02"), counter, ext)
		}
		_, err := os.Lstat(filepath.Join(basePath, newFilename))
		if err == nil {
			continue
		}
		if errors.Is(err, os.ErrNotExist) {
			break
		}
		return tracerr.Wrap(err)
	}
	newFilename = filepath.Join(basePath, newFilename)
	err := os.Rename(h.filename, newFilename)
	if err != nil {
		return tracerr.Wrap(err)
	}
	err = h.openFile()
	if err != nil {
		return tracerr.Wrap(err)
	}
	err = h.cleanBackupFiles()
	if err != nil {
		return tracerr.Wrap(err)
	}
	return nil
}

func (h *Handler) getBackupFile() ([]string, error) {
	dirName, fileName := filepath.Split(h.filename)
	ext := filepath.Ext(fileName)
	var fileNameWithoutExt string
	if len(ext) > 0 {
		fileNameWithoutExt = fileName[:len(fileName)-len(ext)]
	} else {
		fileNameWithoutExt = fileName
	}

	entries, err := os.ReadDir(dirName)
	if err != nil {
		return nil, tracerr.Wrap(err)
	}
	entries = lo.Filter(entries, func(entry os.DirEntry, _ int) bool {
		if entry.IsDir() {
			return false
		}
		name := entry.Name()
		if name == fileName {
			return false
		}
		if !strings.HasPrefix(name, fileNameWithoutExt) || !strings.HasSuffix(name, ext) {
			return false
		}
		return true
	})
	sort.Slice(entries, func(i, j int) bool {
		info1, err1 := entries[i].Info()
		info2, err2 := entries[j].Info()
		if err1 == nil && err2 == nil {
			return info1.ModTime().After(info2.ModTime())
		}
		return false
	})
	filenames := lo.Map(entries, func(entry os.DirEntry, index int) string {
		return filepath.Join(dirName, entry.Name())
	})
	return filenames, nil
}

// cleanBackupFiles 输出旧的备份文件
func (h *Handler) cleanBackupFiles() error {
	filenames, err := h.getBackupFile()
	if err != nil {
		return tracerr.Wrap(err)
	}
	if len(filenames) <= h.maxBackups {
		return nil
	}
	for _, filename := range filenames[h.maxBackups:] {
		err_ := os.Remove(filename)
		if err == nil && err_ != nil {
			err = tracerr.Wrap(err_)
		}
	}
	return err
}
